package be.destin.abdera;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.abdera.ext.history.FeedPagingHelper;
import org.apache.abdera.ext.opensearch.OpenSearchConstants;
import org.apache.abdera.ext.opensearch.model.IntegerElement;
import org.apache.abdera.ext.opensearch.server.processors.OpenSearchTargetTypes;
import org.apache.abdera.factory.Factory;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.ResponseContext;
import org.apache.abdera.protocol.server.TargetType;
import org.apache.abdera.protocol.server.RequestContext.Scope;
import org.apache.abdera.protocol.server.context.ResponseContextException;
import org.apache.abdera.protocol.server.impl.AbstractEntityCollectionAdapter;
import org.apache.abdera.protocol.server.provider.basic.BasicProvider;
import org.apache.log4j.Logger;

import be.destin.skos.core.TermList;
import be.destin.skos.html.SkosHtmlManager;

public abstract class AbstractCorrectedCollectionAdapter<T> extends AbstractEntityCollectionAdapter<T> {

	/*
  void 	deleteEntry(String resourceName, RequestContext request)
          Delete an entry
 String 	getAuthor(RequestContext request)
          The author of this collection.
 List<Person> 	getAuthors(Employee entry, RequestContext request)
          Get the authors for an entry.
 Object 	getContent(Employee entry, RequestContext request)
          Get the content for the entry.
 Iterable<Employee> 	getEntries(RequestContext request)
          Get the listing of entries requested
 Employee 	getEntry(String resourceName, RequestContext request)
          Get a specific entry
 String 	getId(Employee entry)
          Gets the UUID for the specified entry.
 String 	getId(RequestContext request)
          A unique ID for this feed.
 String 	getName(Employee entry)
          Get the name of the entry resource (used to construct links)
 String 	getTitle(Employee entry)
          Get the title fo the entry
 String 	getTitle(RequestContext request)
          The title of our collection.
 Date 	getUpdated(Employee entry)
          Get the value to use in the atom:updated element
 Employee 	postEntry(String title, IRI id, String summary, Date updated, List<Person> authors, Content content, RequestContext request)
          Create a new entry
 void 	putEntry(Employee employee, String title, Date updated, List<Person> authors, String summary, Content content, RequestContext request)
          Update an entry.

	 */

	/** log4j category */
	private static Logger log = Logger.getLogger(AbstractCorrectedCollectionAdapter.class);

	final String source = "ATOM"; 

	protected String language = TermList.anyLanguage;

	public abstract String getIconUrl(RequestContext request);

	@Override
	public String getId(RequestContext context) {
		return getHref();
	}

	public static String getUserLanguage(RequestContext context) {
		String language = context.getContentLanguage();
		if (language == null || language.isEmpty()) {
			language = context.getAcceptLanguage();
		}
		if (language != null && language.length() >= 2) {
			language = language.substring(0,2).toLowerCase();
		} else {
			language = TermList.anyLanguage;
		}
		return language;
	}

	@Override
	public void start(RequestContext context)
			throws ResponseContextException {
		super.start(context);
		language = getUserLanguage(context);
	}

	public void stop(RequestContext context) {
		language = TermList.anyLanguage;
	}

	public final String getContentType() {
		return Content.Type.XML.name(); 
	}

    /**
     * Get's the name of the specific resource requested
     */
	@Override
    protected String getResourceName(RequestContext request) {
        return request.getTarget().getParameter(BasicProvider.PARAM_ENTRY);
    }
	
	protected static int[] helpPaging(RequestContext request, Feed feed, int size, 
										TargetType target, Map<String,String> param ) {

		String count = request.getParameter(AbstractCorrectedWorkspaceProvider.PARAM_COUNT);
		int max = -1;
		if (count == null || count.isEmpty()) {
			count = request.getParameter(AbstractCorrectedWorkspaceProvider.PARAM_TOP);
		}
		if (count != null && !count.isEmpty()) {
			try {
				max = Integer.parseInt(count);
			} catch (NumberFormatException e) {
			}
		}
		int pageSize = max;
		int beg = -1;
		boolean skip = false;
		String start = request.getParameter(AbstractCorrectedWorkspaceProvider.PARAM_STARTINDEX);
		if (start == null || start.isEmpty()) {
			start = request.getParameter(AbstractCorrectedWorkspaceProvider.PARAM_SKIP);
			skip = true;
		}
		if (start != null && !start.isEmpty()) {
			try {
				int index = Integer.parseInt(start);
				if (skip) index++;
				beg = index;
			} catch (NumberFormatException e) {
			}
		}
		if (beg <= 0) beg = 0;
		else beg--;
		if (max <= 0) max = size;
		if (beg > 0 || (beg+max < size)) {
			if (beg+max > size) max = size-beg;
		}
		
		/*
		   <link rel="search"
	         href="http://example.com/opensearchdescription.xml" 
	         type="application/opensearchdescription+xml" 
	         title="Content Search" />
		 */
			/*
			log.error("URL orig="+request.getUri().toString());
			log.error("Sans param="+request.urlFor(request.getTarget().getType(), null));
			String res = "";
			for (String ss: param.keySet()) {
				res += ss+"=["+param.get(ss)+"] ";
			}
			log.error("Params="+res);
			*/
		feed.addLink(request.urlFor(OpenSearchTargetTypes.OPENSEARCH_DESCRIPTION, param), "search");

        String query = param.get(AbstractCorrectedWorkspaceProvider.PARAM_SEARCHTERMS);
		if (query == null || query.isEmpty()) {
			query = param.get(AbstractCorrectedWorkspaceProvider.PARAM_FILTER);
		}
        if (query != null && !query.isEmpty()) {
        	org.apache.abdera.model.Element queryElem = feed.addExtension(OpenSearchConstants.QUERY);
            queryElem.setAttributeValue(OpenSearchConstants.QUERY_ROLE_LN, "request");
            queryElem.setAttributeValue(OpenSearchConstants.QUERY_SEARCHTERMS_LN, query);
        }

		// FeedPagingHelper.setCurrent(feed, "current"); == self
		String link;
		if (pageSize > 0) {
			param.put(AbstractCorrectedWorkspaceProvider.PARAM_COUNT, Integer.toString(pageSize));
			param.put('\\'+AbstractCorrectedWorkspaceProvider.PARAM_TOP, Integer.toString(pageSize));
		} else {
			param.remove(AbstractCorrectedWorkspaceProvider.PARAM_COUNT);
			param.remove('\\'+AbstractCorrectedWorkspaceProvider.PARAM_TOP);
		}
		if (beg > 0) {
			int prvBeg = beg-pageSize;
			if (prvBeg>=0) {
				param.put(AbstractCorrectedWorkspaceProvider.PARAM_STARTINDEX, Integer.toString(prvBeg+1));
				param.put('\\'+AbstractCorrectedWorkspaceProvider.PARAM_SKIP, Integer.toString(prvBeg));
			} else {
				param.remove(AbstractCorrectedWorkspaceProvider.PARAM_STARTINDEX);
				param.remove('\\'+AbstractCorrectedWorkspaceProvider.PARAM_SKIP);
			}
			link = request.urlFor(target, param);
			FeedPagingHelper.setPrevious(feed, link);

			param.remove(AbstractCorrectedWorkspaceProvider.PARAM_STARTINDEX);
			param.remove('\\'+AbstractCorrectedWorkspaceProvider.PARAM_SKIP);
			link = request.urlFor(target, param);
			FeedPagingHelper.setFirst(feed, link);
		}
		if (beg+max < size) {
			int nxtBeg = beg+max;
			param.put(AbstractCorrectedWorkspaceProvider.PARAM_STARTINDEX, Integer.toString(nxtBeg+1));
			param.put('\\'+AbstractCorrectedWorkspaceProvider.PARAM_SKIP, Integer.toString(nxtBeg));

			link = request.urlFor(target, param);
			FeedPagingHelper.setNext(feed, link);
			
			nxtBeg = size % pageSize;
			if (nxtBeg == 0) {
				nxtBeg = size-pageSize;
			} else {
				nxtBeg = size - nxtBeg;
			}
			param.put(AbstractCorrectedWorkspaceProvider.PARAM_STARTINDEX, Integer.toString(nxtBeg+1));
			param.put('\\'+AbstractCorrectedWorkspaceProvider.PARAM_SKIP, Integer.toString(nxtBeg));
			link = request.urlFor(target, param);
			FeedPagingHelper.setLast(feed, link);
		}
		
		int[] range = new int[2];
		range[0] = beg;
		range[1] = max;
		return range;
	}

	protected List<T> helpPaging(RequestContext request, Feed feed, List<T> result, TargetType target, HashMap<String,String> param ) {

		String link = request.urlFor(target, param);
		feed.addLink(link);
		feed.addLink(link, "self");
		feed.addLink(this.getIconUrl(request), "icon");

		int size = result.size();
		int[] range = helpPaging(request,feed,size, target, param);
		int beg = range[0];
		int max = range[1];

		IntegerElement ie = feed.addExtension(OpenSearchConstants.ITEMS_PER_PAGE);
		ie.setValue(max);

		ie = feed.addExtension(OpenSearchConstants.START_INDEX);
		ie.setValue(beg+1);

		ie = feed.addExtension(OpenSearchConstants.TOTAL_RESULTS);
		ie.setValue(size);
		
		if (beg > 0 || (beg+max < size)) {
			if (beg+max > size) max = size-beg;
			result = result.subList(beg, beg+max);
		}
		return result;
	}

	@Override
	protected Feed createFeedBase(RequestContext request) throws ResponseContextException {
		// Feed feed = super.createFeedBase(request); NO NO: puts wrong links!
		Factory factory = request.getAbdera().getFactory();
		Feed feed = factory.newFeed();
		feed.setId(getId(request));
		feed.setTitle(getTitle(request));
		feed.addAuthor(getAuthor(request));
		feed.setUpdated(new Date());

		return feed;
	}

    public ResponseContext getPropertyFeed(RequestContext request, T entity, String navigationProperty) {
        try {
    		Factory factory = request.getAbdera().getFactory();
    		Feed feed = factory.newFeed();
    		feed.setId(getId(request)+'('+getResourceName(request)+')'+navigationProperty);
    		feed.setTitle(getTitle(request)+" ("+getResourceName(request)+") "+navigationProperty);
    		feed.addAuthor(getAuthor(request));
    		feed.setUpdated(new Date());

            addPropertyFeedDetails(feed, request, entity, navigationProperty);

            return buildGetFeedResponse(feed);
        } catch (ResponseContextException e) {
            return createErrorResponse(e);
        }
    }

    public ResponseContext getEntriesListFeed(RequestContext request, String entriesList) {
        try {
    		Factory factory = request.getAbdera().getFactory();
    		Feed feed = factory.newFeed();
    		feed.setId(getId(request)+'('+entriesList+')');
    		feed.setTitle(getTitle(request)+" ("+entriesList+")");
    		feed.addAuthor(getAuthor(request));
    		feed.setUpdated(new Date());

            addEntriesListDetails(feed, request, entriesList);

            return buildGetFeedResponse(feed);
        } catch (ResponseContextException e) {
            return createErrorResponse(e);
        }
    }

    /**
     * Adds the selected entries to the Feed document. By default, this will set the feed's atom:updated element to the
     * current date and time
     */
    protected void addFeedDetails(Feed feed, RequestContext request) throws ResponseContextException {

		HashMap<String,String> param = new HashMap<String,String>();
		param.put("collection", this.getHref());

		List<T> entries = getListEntries(request);
		entries = helpPaging(request,feed, entries,TargetType.TYPE_COLLECTION, param);

		if (entries != null) {
    		for (T entryObj : entries) {
                Entry e = feed.addEntry();

                IRI feedIri = new IRI(getFeedIriForEntry(entryObj, request));
                addEntryDetails(request, e, feedIri, entryObj);

                if (isMediaEntry(entryObj)) {
                    addMediaContent(feedIri, e, entryObj, request);
                } else {
                    addContent(e, entryObj, request);
                }
            }
        }
    }

    /**
     * Adds the selected entries to the Feed document. By default, this will set the feed's atom:updated element to the
     * current date and time
     */
    protected void addPropertyFeedDetails(Feed feed, RequestContext request, T entity, String navigationProperty) throws ResponseContextException {

		HashMap<String,String> param = new HashMap<String,String>();
		param.put("collection", this.getHref());
		param.put("entry", getResourceName(request));
		param.put("navigation", navigationProperty);

		List<T> entries = getPropertyEntries(request, entity, navigationProperty);
		helpPaging(request,feed, entries,AbstractCorrectedWorkspaceProvider.TYPE_ENTRY_LINKS, param);

		if (entries != null) {
    		for (T entryObj : entries) {
                Entry e = feed.addEntry();
                IRI feedIri = new IRI(getFeedIriForEntry(entryObj, request));
                addEntryDetails(request, e, feedIri, entryObj);

                if (isMediaEntry(entryObj)) {
                    addMediaContent(feedIri, e, entryObj, request);
                } else {
                    addContent(e, entryObj, request);
                }
            }
        }
    }

    /**
     * Adds the selected entries to the Feed document. By default, this will set the feed's atom:updated element to the
     * current date and time
     */
    protected void addEntriesListDetails(Feed feed, RequestContext request, String entriesList) throws ResponseContextException {

		HashMap<String,String> param = new HashMap<String,String>();
		param.put("collection", this.getHref());
		param.put("entries", entriesList);

		List<T> entries = getCommaSeparatedEntriesList(request, entriesList);
		helpPaging(request,feed, entries,AbstractCorrectedWorkspaceProvider.TYPE_ENTRIES_LIST, param);

		if (entries != null) {
    		for (T entryObj : entries) {
                Entry e = feed.addEntry();
                IRI feedIri = new IRI(getFeedIriForEntry(entryObj, request));
                addEntryDetails(request, e, feedIri, entryObj);

                if (isMediaEntry(entryObj)) {
                    addMediaContent(feedIri, e, entryObj, request);
                } else {
                    addContent(e, entryObj, request);
                }
            }
        }
    }

    public abstract List<T> getListEntries(RequestContext request) throws ResponseContextException;
    /*
     * (non-Javadoc)
     * @see org.apache.abdera.protocol.server.impl.AbstractEntityCollectionAdapter#getEntries(org.apache.abdera.protocol.server.RequestContext)
     * 
     * Patch to give access to the Iterable size...
     */
    public Iterable<T> getEntries(RequestContext request) throws ResponseContextException {
    	return getListEntries(request);
    }

    public abstract List<T> getPropertyEntries(RequestContext request, T entity, String navigationProperty) throws ResponseContextException;

    public abstract List<T> getCommaSeparatedEntriesList(RequestContext request, String entriesList);

	SkosHtmlManager getSkosHtmlManager(RequestContext request) {
		return (SkosHtmlManager) request.getAttribute(Scope.REQUEST, AskosiProviderServlet.CURRENT_HTML_MANAGER);
	}

}
